/*
 * Copyright (C) Antonio Ramon Morcillo
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package soft.xitxarello.su.loaders;

import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import soft.xitxarello.su.data.App;
import soft.xitxarello.su.helpers.ConfigChangesHelper;
import soft.xitxarello.su.helpers.PackageIntentReceiver;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.support.v4.content.AsyncTaskLoader;

/**
 * A custom Loader that loads all of the uninstallable applications.
 * 
 * @see android.support.v4.content.AsyncTaskLoader
 */
public class AppListLoader extends AsyncTaskLoader<List<App>> {

    // Device configuration information that can impact the resources the
    // application retrieves with changes during data load.
    private ConfigChangesHelper oldConfig = new ConfigChangesHelper();

    // information related to the application packages that are currently
    // installed on the device.
    private final PackageManager pm;

    // Uninstallable applications list.
    private List<App> lApps;

    // look for interesting changes to the installed applications.
    PackageIntentReceiver pObserver;

    /**
     * Default constructor.
     * 
     * @param context Interface to global information about an application
     *            environment.
     */
    public AppListLoader(Context context) {
        super(context);

        // Retrieve the package manager for later use; note we don't
        // use 'context' directly but instead the save global application
        // context returned by getContext().
        pm = getContext().getPackageManager();
    }

    /**
     * Overrides a superclass method. This is where the bulk of our work is
     * done. This function is called in a background thread and should generate
     * a new set of data to be published by the loader.
     */
    @Override
    public List<App> loadInBackground() {

        // Retrieve all known applications.
        List<ApplicationInfo> allApps = pm.getInstalledApplications(
                PackageManager.GET_UNINSTALLED_PACKAGES |
                        PackageManager.GET_DISABLED_COMPONENTS);
        if (allApps == null) {
            allApps = new ArrayList<ApplicationInfo>();
        }

        // Create corresponding array of entries and load their names.
        List<App> entries = new ArrayList<App>(allApps.size());
        for (int i = 0; i < allApps.size(); i++) {
            if ((allApps.get(i).flags & ApplicationInfo.FLAG_SYSTEM) != 1) {
                App entry = new App(allApps.get(i).packageName);
                entry.setName(this.getContext());
                if (entry.getName().length() > 0) {
                    entries.add(entry);
                }
            }
        }

        // Sort the list.
        Collections.sort(entries, ALPHA_COMPARATOR);

        return entries;
    }

    /**
     * Overrides a superclass method. Called when there is new data to deliver
     * to the client. The super class will take care of delivering it; the
     * implementation here just adds a little more logic.
     * 
     * @param apps list of uninstallable applications.
     */
    @Override
    public void deliverResult(List<App> apps) {
        if (isReset()) {
            // An async query came in while the loader is stopped. We
            // don't need the result.
            if (apps != null) {
                onReleaseResources(apps);
            }
        }
        List<App> oldApps = apps;
        lApps = apps;

        if (isStarted()) {
            // If the Loader is currently started, we can immediately
            // deliver its results.
            super.deliverResult(apps);
        }

        // At this point we can release the resources associated with
        // 'oldApps' if needed; now that the new result is delivered we
        // know that it is no longer in use.
        if (oldApps != null) {
            onReleaseResources(oldApps);
        }
    }

    /**
     * Handles a request to start the Loader.
     */
    @Override
    protected void onStartLoading() {
        if (lApps != null) {
            // If we currently have a result available, deliver it
            // immediately.
            deliverResult(lApps);
        }

        // Start watching for changes in the application data.
        if (pObserver == null) {
            pObserver = new PackageIntentReceiver(this);
        }

        // Has something interesting in the configuration changed since we
        // last built the application list?
        boolean configChange = oldConfig.applyNewConfig(getContext().getResources());

        if (takeContentChanged() || lApps == null || configChange) {
            // If the data has changed since the last time it was loaded
            // or is not currently available, start a load.
            forceLoad();
        }
    }

    /**
     * Handles a request to stop the Loader.
     */
    @Override
    protected void onStopLoading() {
        // Attempt to cancel the current load task if possible.
        cancelLoad();
    }

    /**
     * Handles a request to cancel a load.
     * 
     * @param apps list of uninstallable applications.
     */
    @Override
    public void onCanceled(List<App> apps) {
        super.onCanceled(apps);

        // At this point we can release the resources associated with 'apps'
        // if needed.
        onReleaseResources(apps);
    }

    /**
     * Handles a request to completely reset the Loader.
     */
    @Override
    protected void onReset() {
        super.onReset();

        // Ensure the loader is stopped
        onStopLoading();

        // At this point we can release the resources associated with 'apps'
        // if needed.
        if (lApps != null) {
            onReleaseResources(lApps);
            lApps = null;
        }

        // Stop monitoring for changes.
        if (pObserver != null) {
            getContext().unregisterReceiver(pObserver);
            pObserver = null;
        }
    }

    /**
     * Helper function to take care of releasing resources associated with an
     * actively loaded data set.
     * 
     * @param apps list of uninstallable applications.
     */
    protected void onReleaseResources(List<App> apps) {
        // For a simple List<> there is nothing to do. For something
        // like a Cursor, we would close it here.
    }

    /**
     * Getter Method.
     * 
     * @return pm as package manager.
     */
    public PackageManager getPm() {
        return pm;
    }

    /**
     * Perform alphabetical comparison of application entry objects. A
     * Comparator is used to compare two objects to determine their ordering
     * with respect to each other.
     * 
     * @see java.util.Comparator
     */
    public static final Comparator<App> ALPHA_COMPARATOR = new Comparator<App>() {

        // Following the Unicode Consortium's specifications for the Unicode
        // Collation Algorithm (UCA).
        private final Collator sCollator = Collator.getInstance();

        /**
         * Compares the two specified objects to determine their relative
         * ordering.
         * 
         * @param object1 left uninstallable application to compare.
         * @param object2 right uninstallable application to compare.
         * @return an integer < 0 if object1 is less than object2, 0 if they are
         *         equal, and > 0 if object1 is greater than object2.
         */
        @Override
        public int compare(App object1, App object2) {
            return sCollator.compare(object1.getName(), object2.getName());
        }
    };

}
